package com.wltt.healing.healinguserservice.service.impl;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.wltt.healing.healingusercommon.constant.RedisConstant;
import com.wltt.healing.healingusercommon.utils.JsonUtil;
import com.wltt.healing.healingusercommon.utils.RSAUtil;
import com.wltt.healing.healingusercommon.utils.SaltUtil;
import com.wltt.healing.healinguserservice.config.SecurityKeyProperty;
import com.wltt.healing.healinguserservice.config.exception.BusinessException;
import com.wltt.healing.healinguserservice.model.ResultData;
import com.wltt.healing.healinguserservice.model.dto.SecurityKeyDTO;
import com.wltt.healing.healinguserservice.model.vo.LoginReq;
import com.wltt.healing.healinguserservice.model.vo.SecurityKeyRSP;
import com.wltt.healing.healinguserservice.repository.UserRepository;
import com.wltt.healing.healinguserservice.model.po.UserPO;
import com.wltt.healing.healinguserservice.service.UserService;
import org.springframework.data.redis.core.ReactiveRedisTemplate;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono;

import javax.annotation.Resource;
import java.security.NoSuchAlgorithmException;
import java.time.Duration;

import static org.springframework.web.reactive.function.server.ServerResponse.ok;


/**
 * @Author: wltt
 * @Date: 2021/9/8 15:26：
 * @Version: 1.0
 * @Description: TODO
 */
@Service
public class UserServiceImpl extends BaseServiceImpl<UserPO, Long> implements UserService {

    private UserRepository userRepository;

    @Resource
    private ReactiveRedisTemplate<String, String> reactiveRedisTemplate;

    @Resource
    private SecurityKeyProperty securityKeyProperty;


    //构造方法
    public UserServiceImpl(UserRepository userRepository) {
        super(userRepository);
        this.userRepository = userRepository;
    }

    @Override
    public Mono<ServerResponse> securityKey(ServerRequest serverRequest) {
        try {

            // 获取密钥对
            String[] keyPair = RSAUtil.genKeyPair();
            Long timestamp = System.currentTimeMillis();

            // 同步秘钥
            SecurityKeyDTO securityKeyDTO = new SecurityKeyDTO();
            securityKeyDTO.setPrivateKey(keyPair[1]);
            securityKeyDTO.setTimestamp(timestamp);
            String securityKeyJson = JsonUtil.toJson(securityKeyDTO);

            Mono<ResultData> resultDataMono = serverRequest.session()
                    .flatMap(webSession -> {
                        String sessionId = webSession.getId();
                        String key = RedisConstant.getSecurityKey(sessionId);
                        return reactiveRedisTemplate.opsForValue().set(key, securityKeyJson, Duration.ofMinutes(10));
                    }).flatMap(aBoolean -> {
                        if (!aBoolean) {
                            return Mono.error(new BusinessException("秘钥同步异常"));
                        }
                        SecurityKeyRSP securityKeyRSP = new SecurityKeyRSP();
                        securityKeyRSP.setPublicKey(keyPair[0]);
                        securityKeyRSP.setTimestamp(timestamp);
                        return Mono.just(ResultData.success(securityKeyRSP));
                    });
            return ok().contentType(MediaType.APPLICATION_JSON).body(resultDataMono, ResultData.class);
        } catch (NoSuchAlgorithmException | JsonProcessingException e) {
            return Mono.error(new BusinessException("生成安全秘钥异常"));
        }
    }

    @Override
    public Mono<ServerResponse> login(ServerRequest serverRequest) {
        return serverRequest.bodyToMono(LoginReq.class)
                .flatMap(loginReq -> {
                    String userPhone = loginReq.getUserPhone();
                    if (!StringUtils.hasLength(userPhone)) {
                        return Mono.error(new BusinessException("用户名或密码错误"));
                    }
                    String userPassword = loginReq.getUserPassword();
                    if (!StringUtils.hasLength(userPassword)) {
                        return Mono.error(new BusinessException("用户名或密码错误"));
                    }
                    return Mono.just(loginReq);
                }).flatMap(loginReq -> serverRequest.session()
                        .flatMap(webSession -> {
                            String sessionId = webSession.getId();
                            String key = RedisConstant.getSecurityKey(sessionId);
                            return reactiveRedisTemplate.opsForValue().get(key);
                        }).flatMap(s -> Mono.just(JsonUtil.toObject(s, SecurityKeyDTO.class)))
                        .flatMap(securityKeyDTO -> {
                            Long currentTimeMillis = System.currentTimeMillis();
                            if (securityKeyDTO.getTimestamp() != currentTimeMillis) {
                                return Mono.error(new BusinessException("密钥以过期"));
                            }
                            String privateKey = securityKeyDTO.getPrivateKey();
                            try {
                                String userPhone = RSAUtil.encrypt(RSAUtil.decrypt(loginReq.getUserPhone(), privateKey), securityKeyProperty.getPublicKey());

                                return userRepository.findByUserPhone(userPhone)
                                        .flatMap(userPO -> {
                                            try {
                                                String userPasswordMd5 = RSAUtil.decrypt(loginReq.getUserPassword(), privateKey);
                                                // 对传过来的md5加密后的密码加入盐值后再次进行md5后和数据库中进行比对
                                                String userPasswordMd5Md5 = SaltUtil.getSaltString(userPasswordMd5, userPO.getUserSalt());
                                                String userPasswordMd5Md5RSA = RSAUtil.decrypt(userPasswordMd5Md5, securityKeyProperty.getPublicKey());
                                                if (!userPO.getUserPassword().equals(userPasswordMd5Md5RSA)) {
                                                    return Mono.error(new BusinessException("用户名或密码错误"));
                                                }

                                                // TODO 共享用户会话
                                                return ok().contentType(MediaType.APPLICATION_JSON).body(Mono.just(ResultData.success()), ResultData.class);

                                            } catch (Exception e) {
                                                return Mono.error(new BusinessException("用户名或密码错误"));
                                            }

                                        }).switchIfEmpty(Mono.error(new BusinessException("用户名或密码错误")));
                            } catch (Exception e) {
                                return Mono.error(new BusinessException("用户名或密码错误"));
                            }

                        }));
    }
}